/**
 * 获取数据类型
 * @param obj
 * @returns {*}
 */
function get_type(obj) {
    return Object.prototype.toString.call(obj);
}


const TYPE_ARRAY = get_type([]);
const TYPE_STRING = get_type('');
const TYPE_REGEXP = get_type(/^/);
const TYPE_NUMBER = get_type(1);
const TYPE_BOOL = get_type(true);
const TYPE_UNDEFINED = get_type();
const TYPE_OBJECT = get_type({});
const TYPE_FUNCTION = get_type(function(){});

class DataCreator {
    constructor(fixed, def) {
        this.fixed = Type.clone(fixed);
        this.def = Type.clone(def);
    }
    
    merge(val) {
        let fixed = Type.clone(this.fixed);
        let def = Type.clone(this.def);
        let obj = {};
        
        if(Type.getType(fixed) != Type.getType(def)) {
            def = null;
        }
        
        if(Type.getType(fixed) != Type.getType(val)) {
            return fixed;
        }
        
        if(Type.isArray(fixed)) {
            obj = fixed;
            
            if(def) {
                obj = fixed.concat(def);
            }
            
            obj = obj.concat(val);
            return obj;
        } else if(Type.isObject(fixed)) {
            if(def) {
                obj = (new this.constructor({}, def)).merge(val);
            } else {
                obj = val;
            }
    
            let keys = Object.keys(fixed);
    
            for(let i=0; i<keys.length; i++) {
                let key = keys[i];
                obj[key] = (new this.constructor(fixed[key])).merge(obj[key]);
            }
    
            return obj;
        } else {
            return fixed;
        }
    }
}

module.exports = class Type {
    static getType(obj) {
        return get_type(obj);
    }
    
    static isArray(obj) {
        return get_type(obj) == TYPE_ARRAY;
    }
    
    static isString(obj) {
        return get_type(obj) == TYPE_STRING;
    }
    
    static isFunction(obj) {
        return get_type(obj) == TYPE_FUNCTION;
    }
    
    static isRegexp(obj) {
        return get_type(obj) == TYPE_REGEXP;
    }
    
    static isNumber(obj) {
        return get_type(obj) == TYPE_NUMBER;
    }
    
    static isBool(obj) {
        return get_type(obj) == TYPE_BOOL;
    }
    
    static isNull(obj) {
        return obj === null;
    }
    
    static isUndefined(obj) {
        return get_type(obj) == TYPE_UNDEFINED;
    }
    
    static isObject(obj) {
        return get_type(obj) == TYPE_OBJECT;
    }
    
    static isSimpleObject(obj) {
        return this.isObject(obj) && obj.__proto__ && obj.__proto__.constructor.name === 'Object';
    }
    
    static isEmpty(obj) {
        let result = obj===false ||
            this.isUndefined(obj) ||
            this.isNull(obj) ||
            (this.isString(obj) && obj.length == 0) ||
            (this.isArray(obj) && obj.length == 0)
        ;
        
        if(result) {
            return result;
        }
        
        if(this.isObject(obj)) {
            return Object.keys(obj).length == 0;
        }
        
        return false;
    }
    
    static clone(obj) {
        if(this.isArray(obj)) {
            return obj.concat([]);
        } else if(Type.isObject(obj)) {
            let cloneObj = {};
            
            for(let i in obj) {
                cloneObj[i] = this.clone(obj[i]);
            }
            
            return cloneObj;
        } else {
            return obj;
        }
    }
    
    static merge() {
        return Object.assign.apply(Object, [...arguments]);
    }
    
    static deepMerge(option) {
        let mergeArray = option.merge_array;
        let clone = option.clone;
        
        let _deepmerge = (a, b) => {
            if(Type.getType(a) != Type.getType(b)) {
                return b;
            }
            
            if(this.isArray(a)) {
                if(mergeArray) {
                    return [...new Set(a.concat(b))];
                } else {
                    return b;
                }
            } else if(this.isObject(a)) {
                let keys = Object.keys(b);
                let obj = clone?this.clone(a):a;
                
                for(let i=0; i<b.length; i++) {
                    let key = keys[i];
                    obj[key] = _deepmerge(obj[key], b[key]);
                }
            } else {
                return b;
            }
        };
        
        let params = option.params;
        let obj = {};
        
        for(let i=params.length-1; i>=0; i--) {
            obj = _deepmerge(obj, params[i]);
        }
        
        return obj;
    }
    
    static cloneMerge() {
        return Object.assign.apply(Object, [...arguments].map( i => this.clone(i) ));
    }
    
    static extend() {
        let args = [...arguments];
        let extendObj = args.shift();
        
        if(args.length == 0) {
            return extendObj;
        }
        
        Object.assign.apply(Object, [extendObj].concat(args.map(r => this.clone(r))));
        return extendObj;
    }
    
    static extendPrivate(obj, props) {
        let newProps = {};
        
        for(let i in props) {
            newProps [i]= {
                value: props[i],
                writeable:false
            }
        }
        
        Object.defineProperties(obj, newProps);
        return obj;
    }
    
    static instanceOf(instance, classType) {
        if(instance instanceof classType) {
            return true;
        }
        
        if(!classType || !classType.name) {
            return false;
        }
        
        let lastProtoName;
        
        for(;;) {
            let proto = instance.__proto__;
            
            if(lastProtoName && lastProtoName == proto.constructor.name) {
                break;
            }
            
            if(proto.constructor.name == classType.name) {
                return true;
            }
    
            lastProtoName = proto.constructor.name;
        }
        
        return false;
    }
    
    static legalizeData(data, tpl) {
        if(this.isArray(tpl)) {
            if(!this.isArray(data)) {
                return [].concat(tpl);
            }
            
            for(let i=0; i<data.length; i++) {
                data[i] = this.legalizeData(data[i], tpl[0]);
            }
        } else if(this.isObject(tpl)) {
            if(!this.isObject(data)) {
                data = {};
            }
            
            let keys = Object.keys(tpl);
            
            for(let i=0; i<keys.length; i++) {
                let key = keys[i];
                data[key] = this.legalizeData(data[key], tpl[key]);
            }
        }
        
        return this.isUndefined(data) ? tpl : data;
    }
    
    static dataCreator(fixed, def) {
        return new DataCreator(fixed, def);
    }
};


